using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;

namespace YouTubeDupe
{
    /// <summary>
    /// The ResultsServer module of the YouTube Duplicate Video Finder.
    /// </summary>
    public class ResultsServer
    {
        SessionController _controller;

        string _pageSource;
        string _resultsSource;

        HttpListener _pageServer;
        HttpListener _functionServer;

        Thread _backgroundThread;
        Thread _functionServerThread;
        Thread _pageServerThread;

        int _videosCompared = 0;

        string resultsPath;

        Dictionary<Video, List<Video>> _comparedVideos = new Dictionary<Video, List<Video>>();

        /// <summary>
        /// Instantiates an instance of the results server module.
        /// </summary>
        /// <param name="controller">The controller for module.</param>
        public ResultsServer(SessionController controller)
        {
            if (controller == null)
            {
                throw new NullReferenceException("Controller can't be null.");
            }

            _controller = controller;

            _pageSource = Resources.ResultsServerPageSource;

            // Configure the function server.
            _functionServer = new HttpListener();
            _functionServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/resultsServer/functionServer/");
            _functionServerThread = new Thread(new ThreadStart(functionServerProcess));

            // Configure the page server.
            _pageServer = new HttpListener();
            _pageServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/resultsServer/pageServer/");
            _pageServerThread = new Thread(new ThreadStart(pageServerProcess));

            // Configure the background thread.
            _backgroundThread = new Thread(new ThreadStart(backgroundProcess));
        }

        /// <summary>
        /// Starts the module and accept incoming requests.
        /// </summary>
        public void Start()
        {
            // Start the http listeners.
            _functionServer.Start();
            _pageServer.Start();

            // Start the http listener threads.
            _backgroundThread.Start();
            _functionServerThread.Start();
            _pageServerThread.Start();
        }

        /// <summary>
        /// Stops the module and rejects all incoming requests.
        /// </summary>
        public void Stop()
        {
            // Stop the http listeners.
            _pageServer.Abort();
            _functionServer.Abort();

            // Stop all threads.
            _backgroundThread.Abort();
            _pageServerThread.Abort();
            _functionServerThread.Abort();
        }

        /// <summary>
        /// The function server process. Accepts incoming http requests and sends appropriate responses.
        /// </summary>
        private void functionServerProcess()
        {
            while (true)
            {
                try
                {
                    // Get an incoming request.
                    HttpListenerContext context = _functionServer.GetContext();

                    // Get the function name of the request.
                    string functionName = context.Request.QueryString["function"];

                    // Return if no function name is specified.
                    if (functionName == null) return;

                    context.Response.AppendHeader("Cache-Control", "no-cache");

                    switch (functionName)
                    {
                        case "getCompareCount":
                            {
                                // Get the current compare count.
                                using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                                {
                                    if (_videosCompared == _controller.UserSpecifiedVideoCount)
                                    {
                                        // If the videos compared equals the specified user count, write an operation complete response.
                                        writer.Write("All videos compared.");
                                    }
                                    else
                                    {
                                        // Else write the number of videos compared.
                                        writer.Write(_videosCompared + " of " + _controller.UserSpecifiedVideoCount + " videos compared.");
                                    }
                                    writer.Close();
                                }
                                break;
                            }
                        case "getResultsUrl":
                            {
                                // Create the results file path.
                                resultsPath = AppDomain.CurrentDomain.BaseDirectory + "\\results\\" + System.Web.HttpUtility.UrlEncode(_controller.SearchTerm) + ".html";

                                //
                                // generate results here
                                //
                                genResults();

                                // Writes back the location of the results page.
                                using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                                {
                                    writer.Write(resultsPath);
                                    System.Diagnostics.Process.Start(resultsPath);
                                    writer.Close();
                                }
                                
                                // Process finished.
                                OnModuleChanging(EventArgs.Empty);
                                break;
                            }
                    }

                    // Close the response.
                    context.Response.Close();
                }
                // The catchers below are needed for when this process's thread is stopped.
                catch (HttpListenerException) { }
                catch (ObjectDisposedException) { }
            }
        }

        /// <summary>
        /// Generates the results page.
        /// </summary>
        private void genResults()
        {
            string titleReplaceString = @"<!-- SEARCH STRING GOES HERE -->";
            string videoReplaceString = @"<!-- GENERATED RESULTS GO HERE -->";

            _resultsSource = Resources.ResultsTemplate;

            // Set the results page title.
            StringBuilder sb = new StringBuilder(_resultsSource);
            sb = sb.Replace(titleReplaceString, _controller.SearchTerm);

            // A counter to determine the iteration we're on.
            int iterationCounter = 1;

            string startVideo = @"<!-- start video -->";
            string endVideo = @"<!-- end video -->";

            _resultsSource = sb.ToString();
            int videoHtmlStart = _resultsSource.IndexOf(startVideo);
            int videoHtmlLength = _resultsSource.IndexOf(endVideo) + endVideo.Length - videoHtmlStart;

            StringBuilder videoHtml = new StringBuilder(_resultsSource.Substring(videoHtmlStart, videoHtmlLength));
            StringBuilder htmlToInsert = new StringBuilder();

            // Iterate through each parent video and its children and generate the html.
            Dictionary<Video, List<Video>>.Enumerator e = _comparedVideos.GetEnumerator();
            while (e.MoveNext())
            {
                if (iterationCounter++ % 2 == 1)
                {
                    htmlToInsert.AppendLine("<div class=\"lightDiv\">");
                }
                else
                {
                    htmlToInsert.AppendLine("<div class=\"darkDiv\">");
                }

                htmlToInsert.AppendLine(generateVideoHtml(e.Current.Key, videoHtml, false));

                foreach (Video video in e.Current.Value)
                {
                    htmlToInsert.AppendLine(generateVideoHtml(video, videoHtml, true));
                }

                htmlToInsert.AppendLine("</div>");
            }

            string resultsDirFilePath = resultsPath.Substring(0, resultsPath.LastIndexOf("\\"));

            if (!System.IO.Directory.Exists(resultsDirFilePath))
            {
                System.IO.Directory.CreateDirectory(resultsDirFilePath);
            }

            using (System.IO.StreamWriter writer = System.IO.File.CreateText(resultsPath))
            {
                sb = sb.Replace(videoHtml.ToString(), "");
                sb = sb.Replace(videoReplaceString, htmlToInsert.ToString());
                writer.Write(sb.ToString());
                writer.Close();
            }
        }

        /// <summary>
        /// Generates the results page HTML for a given video.
        /// </summary>
        /// <param name="video">The video to generate the HTML for.</param>
        /// <returns>The generated HTML.</returns>
        private string generateVideoHtml(Video video, StringBuilder videoHtml, bool needsPadding)
        {
            StringBuilder videoHtmlCopy = new StringBuilder(videoHtml.ToString());

            if (needsPadding)
            {
                videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- PADDING GOES HERE -->", "<div class=\"vpadding\">");
                videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- PADDING END GOES HERE -->", "</div>");
            }

            string videoTimeString = new TimeSpan(0, 0, int.Parse(video.Length)).ToString();
            if (videoTimeString.StartsWith("00:"))
            {
                videoTimeString = videoTimeString.Remove(0, 3);
            }

            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO URL HERE -->", video.URL);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- THUMBNAIL URL HERE -->", video.ThumbnailURL);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO TITLE HERE -->", video.Title);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO LENGTH HERE -->", videoTimeString);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO DESCRIPTION HERE -->", video.Description);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- AUTHOR NAME HERE -->", video.Author);
            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO VIEWS HERE -->", video.ViewCount);

            string[] tags = video.Tags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder tagBuilder = new StringBuilder();
            bool firstVideo = true;
            foreach (string tag in tags)
            {
                if (!firstVideo)
                {
                    tagBuilder.Append(@" &nbsp; ");
                }
                tagBuilder.Append("<a href=\"/results?search_query=" + tag + "\" class=\"dg\">" + tag + "</a>");
                firstVideo = false;
            }

            videoHtmlCopy = videoHtmlCopy.Replace(@"<!-- VIDEO TAGS HERE -->", tagBuilder.ToString());

            return videoHtmlCopy.ToString();
        }

        /// <summary>
        /// The page server process. Accepts incoming http requests and sends the html page back.
        /// </summary>
        private void pageServerProcess()
        {
            while (true)
            {
                try
                {
                    HttpListenerContext context = _pageServer.GetContext();
                    context.Response.AppendHeader("Cache-Control", "no-cache");
                    using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                    {
                        writer.Write(_pageSource);
                        writer.Close();
                    }
                    context.Response.Close();
                }
                // The catchers below are needed for when this process's thread is stopped.
                catch (HttpListenerException) { }
                catch (ObjectDisposedException) { }
            }
        }

        

        /// <summary>
        /// The background process. Compares all the videos.
        /// </summary>
        private void backgroundProcess()
        {
            while (_videosCompared < _controller.UserSpecifiedVideoCount)
            {
                if (_controller.VideoQueue.Count == 0)
                {
                    // If no videos are in the list, sleep for a second.
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    // Else compare the videos.
                    Video currentVideo = null;

                    //System.Threading.Thread.Sleep(1000);
                    lock (_controller.VideoQueue)
                    {
                        currentVideo = _controller.VideoQueue.Dequeue();
                    }
                    compareVideo(currentVideo);
                    _videosCompared++;
                }
            }
        }

        /// <summary>
        /// Compares a given video to the videos already compared.
        /// </summary>
        /// <param name="videoToCompare">The video to compare.</param>
        private void compareVideo(Video videoToCompare)
        {
            Dictionary<Video, List<Video>>.Enumerator e = _comparedVideos.GetEnumerator();
            bool parentFound = false;
            while (e.MoveNext())
            {
                if (e.Current.Key.Equals(videoToCompare, _controller.Tolerance))
                {
                    e.Current.Value.Add(videoToCompare);
                    parentFound = true;
                    break;
                }
            }

            if (!parentFound)
            {
                _comparedVideos.Add(videoToCompare, new List<Video>());
            }
        }

        /// <summary>
        /// Triggered when a property needs to change in the controller.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Handes the PropertyChanged event.
        /// </summary>
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Triggered when module control is changing.
        /// </summary>
        public event EventHandler ModuleChanging;

        /// <summary>
        /// Handles the ModuleChanging event.
        /// </summary>
        private void OnModuleChanging(EventArgs e)
        {
            if (ModuleChanging != null)
            {
                ModuleChanging(this, e);
            }
        }

        /// <summary>
        /// Gets the # of videos compared.
        /// </summary>
        public int VideosCompared
        {
            get { return _videosCompared; }
        }
    }
}
